
// ===============================================================================================================
// -*- C++ -*-
//
// MeshRendering.cpp - Mesh rendering code.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <GfxLib.hpp>
#include <MeshLib.hpp>

namespace GfxLib {

void RenderMesh(const MeshLib::Mesh * mesh, RenderMode renderMode)
{
	assert(mesh != 0 && defaultShader != 0 && defaultMaterial != 0);

	// Render a mesh with OpenGL draw calls

	int i;
	Material * mat;
	GLenum triRenderMode, quadRenderMode;

	switch (renderMode)
	{
	case RENDER_SOLID:
		triRenderMode = GL_TRIANGLES;
		quadRenderMode = GL_QUADS;
		break;

	case RENDER_POINTS:
		triRenderMode = quadRenderMode = GL_POINTS;
		break;

	case RENDER_WIREFRAME:
		triRenderMode = quadRenderMode = GL_LINE_STRIP;
		break;

	default:
		CommLib::DbgPrintf(PRINT_ERROR, "Invalid mesh render mode!");
		return;
	} // End switch (renderMode)

	// Draw all the groups:
	MeshLib::Mesh::GroupMap::const_iterator groupIndex = mesh->polyGroups.begin();
	MeshLib::Mesh::GroupMap::const_iterator groupEnd = mesh->polyGroups.end();

	// Set per-pixel lighting shader
	GfxLib::defaultShader->Enable();
	GfxLib::defaultShader->SetUniform1i("numEnabledLights", GfxLib::numEnabledLights);

	while (groupIndex != groupEnd)
	{
		if (!(*groupIndex).second->drawable)
		{
			++groupIndex;
			continue;
		}

		MeshLib::Mesh::MaterialMap::const_iterator groupMaterial = mesh->materials.find((*groupIndex).second->materialName);
		MeshLib::Mesh::MaterialMap::const_iterator noMaterial = mesh->materials.end();

		if (groupMaterial != noMaterial)
		{
			mat = (*groupMaterial).second;
		}
		else
		{
			// No material, use default.
			mat = defaultMaterial;
		}

		if (mat->diffuseMap != 0)
		{
			GfxLib::defaultShader->SetUniform1i("hasTexture", 1);
			mat->diffuseMap->Bind();
		}
		else
		{
			GfxLib::defaultShader->SetUniform1i("hasTexture", 0);
		}

		for (i = 0; i < GfxLib::numEnabledLights; ++i)
		{
			glLightfv(GL_LIGHT0 + i, GL_AMBIENT,  mat->ambientColor.v);
			glLightfv(GL_LIGHT0 + i, GL_DIFFUSE,  mat->diffuseColor.v);
			glLightfv(GL_LIGHT0 + i, GL_SPECULAR, mat->specularColor.v);
		}

		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mat->shininess);

		// Draw all the triangles:
		unsigned int nTri = (*groupIndex).second->trangles.size();

		while (nTri--)
		{
			const MeshLib::Triangle & tri = (*groupIndex).second->trangles[nTri];

			glBegin(triRenderMode);

			switch (tri.flags)
			{
			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE):
				glNormal3fv(mesh->normals[tri.normalIndex[0]].v);
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[0]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[0]].v);
				glNormal3fv(mesh->normals[tri.normalIndex[1]].v);
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[1]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[1]].v);
				glNormal3fv(mesh->normals[tri.normalIndex[2]].v);
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[2]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[2]].v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE):
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[0]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[0]].v);
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[1]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[1]].v);
				glTexCoord2fv(mesh->texCoords[tri.texCoordIndex[2]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[2]].v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS):
				glNormal3fv(mesh->normals[tri.normalIndex[0]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[0]].v);
				glNormal3fv(mesh->normals[tri.normalIndex[1]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[1]].v);
				glNormal3fv(mesh->normals[tri.normalIndex[2]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[2]].v);
				break;

			case MeshLib::POLY_HAS_VERTEX:
				glVertex3fv(mesh->vertices[tri.vertexIndex[0]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[1]].v);
				glVertex3fv(mesh->vertices[tri.vertexIndex[2]].v);
				break;
			} // End switch (tri.flags)

			glEnd();
		}

		// Draw all the quads:
		unsigned int nQuad = (*groupIndex).second->quads.size();

		while (nQuad--)
		{
			const MeshLib::Quad & quad = (*groupIndex).second->quads[nQuad];

			glBegin(quadRenderMode);

			switch (quad.flags)
			{
			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE):
				glNormal3fv(mesh->normals[quad.normalIndex[0]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[0]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[0]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[1]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[1]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[1]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[2]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[2]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[2]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[3]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[3]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[3]].v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE):
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[0]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[0]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[1]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[1]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[2]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[2]].v);
				glTexCoord2fv(mesh->texCoords[quad.texCoordIndex[3]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[3]].v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS):
				glNormal3fv(mesh->normals[quad.normalIndex[0]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[0]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[1]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[1]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[2]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[2]].v);
				glNormal3fv(mesh->normals[quad.normalIndex[3]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[3]].v);
				break;

			case MeshLib::POLY_HAS_VERTEX:
				glVertex3fv(mesh->vertices[quad.vertexIndex[0]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[1]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[2]].v);
				glVertex3fv(mesh->vertices[quad.vertexIndex[3]].v);
				break;
			} // End switch (quad.flags)

			glEnd();
		}

		++groupIndex; // Next group.
	}

	GfxLib::defaultShader->Disable();
}

void InterpolateAndRenderMesh(const MeshLib::Mesh * mesh1, const MeshLib::Mesh * mesh2, float interp, RenderMode renderMode)
{
	assert(mesh1 != 0 && mesh2 != 0 && defaultShader != 0 && defaultMaterial != 0);

	// Render a mesh with OpenGL draw calls

	int i;
	Material * mat;
	GLenum triRenderMode, quadRenderMode;

	switch (renderMode)
	{
	case RENDER_SOLID:
		triRenderMode = GL_TRIANGLES;
		quadRenderMode = GL_QUADS;
		break;

	case RENDER_POINTS:
		triRenderMode = quadRenderMode = GL_POINTS;
		break;

	case RENDER_WIREFRAME:
		triRenderMode = quadRenderMode = GL_LINE_STRIP;
		break;

	default:
		CommLib::DbgPrintf(PRINT_ERROR, "Invalid mesh render mode!");
		return;
	} // End switch (renderMode)

	// Draw all the groups:
	MeshLib::Mesh::GroupMap::const_iterator groupIndex = mesh1->polyGroups.begin();
	MeshLib::Mesh::GroupMap::const_iterator groupEnd = mesh1->polyGroups.end();

	// Set per-pixel lighting shader
	GfxLib::defaultShader->Enable();
	GfxLib::defaultShader->SetUniform1i("numEnabledLights", GfxLib::numEnabledLights);

	while (groupIndex != groupEnd)
	{
		if (!(*groupIndex).second->drawable)
		{
			++groupIndex;
			continue;
		}

		MeshLib::Mesh::MaterialMap::const_iterator groupMaterial = mesh1->materials.find((*groupIndex).second->materialName);
		MeshLib::Mesh::MaterialMap::const_iterator noMaterial = mesh1->materials.end();

		if (groupMaterial != noMaterial)
		{
			mat = (*groupMaterial).second;
		}
		else
		{
			// No material, use default.
			mat = defaultMaterial;
		}

		if (mat->diffuseMap != 0)
		{
			GfxLib::defaultShader->SetUniform1i("hasTexture", 1);
			mat->diffuseMap->Bind();
		}
		else
		{
			GfxLib::defaultShader->SetUniform1i("hasTexture", 0);
		}

		for (i = 0; i < GfxLib::numEnabledLights; ++i)
		{
			glLightfv(GL_LIGHT0 + i, GL_AMBIENT,  mat->ambientColor.v);
			glLightfv(GL_LIGHT0 + i, GL_DIFFUSE,  mat->diffuseColor.v);
			glLightfv(GL_LIGHT0 + i, GL_SPECULAR, mat->specularColor.v);
		}

		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mat->shininess);

		// Draw all the triangles:
		unsigned int nTri = (*groupIndex).second->trangles.size();

		while (nTri--)
		{
			const MeshLib::Triangle & tri = (*groupIndex).second->trangles[nTri];

			glBegin(triRenderMode);

			switch (tri.flags)
			{
			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE):
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[0]], mesh2->normals[tri.normalIndex[0]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[0]], mesh2->texCoords[tri.texCoordIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[0]], mesh2->vertices[tri.vertexIndex[0]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[1]], mesh2->normals[tri.normalIndex[1]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[1]], mesh2->texCoords[tri.texCoordIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[1]], mesh2->vertices[tri.vertexIndex[1]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[2]], mesh2->normals[tri.normalIndex[2]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[2]], mesh2->texCoords[tri.texCoordIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[2]], mesh2->vertices[tri.vertexIndex[2]], interp).v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE):
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[0]], mesh2->texCoords[tri.texCoordIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[0]], mesh2->vertices[tri.vertexIndex[0]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[1]], mesh2->texCoords[tri.texCoordIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[1]], mesh2->vertices[tri.vertexIndex[1]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[tri.texCoordIndex[2]], mesh2->texCoords[tri.texCoordIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[2]], mesh2->vertices[tri.vertexIndex[2]], interp).v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS):
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[0]], mesh2->normals[tri.normalIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[0]], mesh2->vertices[tri.vertexIndex[0]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[1]], mesh2->normals[tri.normalIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[1]], mesh2->vertices[tri.vertexIndex[1]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[tri.normalIndex[2]], mesh2->normals[tri.normalIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[2]], mesh2->vertices[tri.vertexIndex[2]], interp).v);
				break;

			case MeshLib::POLY_HAS_VERTEX:
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[0]], mesh2->vertices[tri.vertexIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[1]], mesh2->vertices[tri.vertexIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[tri.vertexIndex[2]], mesh2->vertices[tri.vertexIndex[2]], interp).v);
				break;
			} // End switch (tri.flags)

			glEnd();
		}

		// Draw all the quads:
		unsigned int nQuad = (*groupIndex).second->quads.size();

		while (nQuad--)
		{
			const MeshLib::Quad & quad = (*groupIndex).second->quads[nQuad];

			glBegin(quadRenderMode);

			switch (quad.flags)
			{
			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS | MeshLib::POLY_HAS_TEXTURE):
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[0]], mesh2->normals[quad.normalIndex[0]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[0]], mesh2->texCoords[quad.texCoordIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[0]], mesh2->vertices[quad.vertexIndex[0]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[1]], mesh2->normals[quad.normalIndex[1]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[1]], mesh2->texCoords[quad.texCoordIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[1]], mesh2->vertices[quad.vertexIndex[1]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[2]], mesh2->normals[quad.normalIndex[2]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[2]], mesh2->texCoords[quad.texCoordIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[2]], mesh2->vertices[quad.vertexIndex[2]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[3]], mesh2->normals[quad.normalIndex[3]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[3]], mesh2->texCoords[quad.texCoordIndex[3]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[3]], mesh2->vertices[quad.vertexIndex[3]], interp).v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_TEXTURE):
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[0]], mesh2->texCoords[quad.texCoordIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[0]], mesh2->vertices[quad.vertexIndex[0]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[1]], mesh2->texCoords[quad.texCoordIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[1]], mesh2->vertices[quad.vertexIndex[1]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[2]], mesh2->texCoords[quad.texCoordIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[2]], mesh2->vertices[quad.vertexIndex[2]], interp).v);
				glTexCoord2fv(MathLib::LinearInterpolation(mesh1->texCoords[quad.texCoordIndex[3]], mesh2->texCoords[quad.texCoordIndex[3]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[3]], mesh2->vertices[quad.vertexIndex[3]], interp).v);
				break;

			case (MeshLib::POLY_HAS_VERTEX | MeshLib::POLY_HAS_NORMALS):
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[0]], mesh2->normals[quad.normalIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[0]], mesh2->vertices[quad.vertexIndex[0]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[1]], mesh2->normals[quad.normalIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[1]], mesh2->vertices[quad.vertexIndex[1]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[2]], mesh2->normals[quad.normalIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[2]], mesh2->vertices[quad.vertexIndex[2]], interp).v);
				glNormal3fv(MathLib::LinearInterpolation(mesh1->normals[quad.normalIndex[3]], mesh2->normals[quad.normalIndex[3]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[3]], mesh2->vertices[quad.vertexIndex[3]], interp).v);
				break;

			case MeshLib::POLY_HAS_VERTEX:
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[0]], mesh2->vertices[quad.vertexIndex[0]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[1]], mesh2->vertices[quad.vertexIndex[1]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[2]], mesh2->vertices[quad.vertexIndex[2]], interp).v);
				glVertex3fv(MathLib::LinearInterpolation(mesh1->vertices[quad.vertexIndex[3]], mesh2->vertices[quad.vertexIndex[3]], interp).v);
				break;
			} // End switch (quad.flags)

			glEnd();
		}

		++groupIndex; // Next group.
	}

	GfxLib::defaultShader->Disable();
}

}; // namespace GfxLib {}